ALB のターゲットとして VPC Lambda を指定する構成を CDK でやってみた
はじめに
テントの中から失礼します、CX事業本部のてんとタカハシです!
ALB のターゲットに VPC Lambda を指定する構成を CDK でやってみたので、記事にしようと思います。
この構成は、Lambda から RDS に繋げたり、固定IP を持った Nat Gateway 経由で外部サービスにアクセスするなどの用途で使えそうです。
ソースコードは下記のリポジトリにも置いてあります。
GitHub - iam326/alb-vpc-lambda-sample-cdk
環境
環境は下記の通りです。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H2 $ cdk --version 1.85.0 (build 5f44668) $ yarn --version 1.22.10 $ node --version v12.19.0
単純な構成
構成図
構成は下記の通りです。ALB と VPC Lambda を単純に紐づけているだけの構成です。
実装
スタックの実装は下記の通りです。ターゲットグループのターゲットとして VPC Lambda を指定します。
import * as cdk from '@aws-cdk/core'; import { Vpc, SubnetType, SecurityGroup, Peer, Port } from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { LambdaTarget } from '@aws-cdk/aws-elasticloadbalancingv2-targets'; import * as lambda from '@aws-cdk/aws-lambda'; export class AlbVpcLambdaSampleCdkStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const prefix: string = this.node.tryGetContext('projectName'); const vpc = new Vpc(this, 'Vpc', { cidr: '10.0.0.0/16', enableDnsHostnames: true, enableDnsSupport: true, subnetConfiguration: [ { cidrMask: 24, name: 'PublicSubnet', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'PrivateSubnet', subnetType: SubnetType.PRIVATE, }, ], natGateways: 2, maxAzs: 2, }); const securityGroup = new SecurityGroup(this, 'SecurityGroup', { securityGroupName: `${prefix}-sg`, vpc, }); securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(80)); const alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', { vpc, vpcSubnets: vpc.selectSubnets({ subnetGroupName: 'PublicSubnet' }), loadBalancerName: `${prefix}-alb`, internetFacing: true, securityGroup, }); const helloWorldFunction = new lambda.Function(this, 'HelloWorldFunction', { code: lambda.Code.fromAsset('dist/hello-world'), functionName: `${prefix}-hello-world`, handler: 'index.handler', runtime: lambda.Runtime.NODEJS_12_X, timeout: cdk.Duration.seconds(10), memorySize: 128, vpc, }); const albTargetGroup = new elbv2.ApplicationTargetGroup( this, 'AlbTargetGroup', { vpc, targetGroupName: `${prefix}-tg`, targetType: elbv2.TargetType.LAMBDA, targets: [new LambdaTarget(helloWorldFunction)], } ); alb.addListener('AlbListener', { protocol: elbv2.ApplicationProtocol.HTTP, port: 80, defaultTargetGroups: [albTargetGroup], }); } }
Lambda の実装は下記の通りです。Content-Type
を指定しないとapplication/octet-stream
で返してしまうので、application/json
を指定しています。
export async function handler(): Promise<any> { return { statusCode: 200, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ hello: 'world' }), }; }
動作確認
curl で ALB にアクセスすると、期待通りのレスポンスが返ってきました。
$ curl xxxxx.ap-northeast-1.elb.amazonaws.com {"hello":"world"}
複数の VPC Lambda でパスルーティングする構成
構成図
構成は下記の通りです。アクセスされた HTTP メソッドとパスによって、振り分け先を変えています。
実装
先ほどの実装をベースに変更点を記載します。
export class AlbVpcLambdaSampleCdkStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { ... // 新しい Lambda を作成。返す値以外は同じコード。 const HogeFugaPiyoFunction = new lambda.Function( this, 'HogeFugaPiyoFunction', { code: lambda.Code.fromAsset('dist/hoge'), functionName: `${prefix}-hoge-fuga-piyo`, handler: 'index.handler', runtime: lambda.Runtime.NODEJS_12_X, timeout: cdk.Duration.seconds(10), memorySize: 128, vpc, } ); // これは不要なのでコメントアウト // const albTargetGroup = new elbv2.ApplicationTargetGroup( // this, // 'AlbTargetGroup', // { // vpc, // targetGroupName: `${prefix}-tg`, // targetType: elbv2.TargetType.LAMBDA, // targets: [new LambdaTarget(helloWorldFunction)], // } // ); // defaultTargetGroups ではなく、defaultAction として、404 NotFound を返す設定を入れる const listener = alb.addListener('AlbListener', { protocol: elbv2.ApplicationProtocol.HTTP, port: 80, // defaultTargetGroups: [albTargetGroup], defaultAction: elbv2.ListenerAction.fixedResponse(404, { contentType: elbv2.ContentType.TEXT_PLAIN, messageBody: 'NotFound', }), }); // GET /hello であれば、helloWorldFunction に繋ぐ listener.addTargets('AlbListenerTargetHello', { priority: 1, conditions: [ elbv2.ListenerCondition.httpRequestMethods(['GET']), elbv2.ListenerCondition.pathPatterns(['/hello']), ], targets: [new LambdaTarget(helloWorldFunction)], }); // POST /hoge であれば、HogeFugaPiyoFunction に繋ぐ listener.addTargets('AlbListenerTargetHoge', { priority: 2, conditions: [ elbv2.ListenerCondition.httpRequestMethods(['POST']), elbv2.ListenerCondition.pathPatterns(['/hoge']), ], targets: [new LambdaTarget(HogeFugaPiyoFunction)], }); } }
デプロイすると、ALB のリスナールールは下記の通りになります。
動作確認
指定する HTTP メソッドとパスによって、レスポンスが変わるようになりました。存在しないパスにアクセスした場合はNotFound
を返すようになっています。
$ curl xxxxx.ap-northeast-1.elb.amazonaws.com/hello {"hello":"world"} $ curl -X POST xxxxx.ap-northeast-1.elb.amazonaws.com/hoge {"fuga":"piyo"} $ curl xxxxx.ap-northeast-1.elb.amazonaws.com/foo NotFound
おわりに
新年1本目の記事になりました。今月中に残り3本は書こうと思っています、がんばるぞお〜。
今回は以上になります。最後まで読んで頂きありがとうございました!